package com.hjy.utils;

import cn.hutool.core.util.StrUtil;
import com.hjy.entity.ConfigProperties;
import com.hjy.mapper.SqlMapper;
import com.sun.istack.internal.NotNull;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 *  用于获取数据
 */
@Component
public class DataUtil {

    @Autowired
    private SqlMapper sqlMapper;

    @Autowired
    private ConfigProperties configProperties;

    private final List<String> existTemplateName = new ArrayList<>();

    /**
     * 类型转换相关的 Map
     * @return
     */
    public HashMap<String, String> typeConversionMap() {
        HashMap<String, String> typeConversionMap = new HashMap<>();
        typeConversionMap.put("tinyint","Integer");
        typeConversionMap.put("smallint","Integer");
        typeConversionMap.put("mediumint","Integer");
        typeConversionMap.put("int","Integer");
        typeConversionMap.put("integer","Integer");
        typeConversionMap.put("bigint","Long");
        typeConversionMap.put("float","Float");
        typeConversionMap.put("double","Double");
        typeConversionMap.put("decimal","BigDecimal");
        typeConversionMap.put("bit","Boolean");
        typeConversionMap.put("char","String");
        typeConversionMap.put("varchar","String");
        typeConversionMap.put("tinytext","String");
        typeConversionMap.put("text","String");
        typeConversionMap.put("mediumtext","String");
        typeConversionMap.put("longtext","String");
        typeConversionMap.put("date","LocalDate");
        typeConversionMap.put("datetime","LocalDateTime");
        typeConversionMap.put("timestamp","Date");
        return typeConversionMap;
    }

    /**
     * 获取构造模板的数据
     * @return
     */
    public HashMap<String, Object> getTemplateData(String tableName) {
        // 类型转换 Map
        HashMap<String, String> typeConversionMap = typeConversionMap();
        // 构造数据
        List<Map<String, String>> columnsInfo = sqlMapper.queryColumns(tableName);
        Map<String, String> tableInfo = sqlMapper.queryTable(tableName);
        // 数据
        HashMap<String, Object> module = new HashMap<>();
        // 获取所有的数据表名称
        List<String> list = sqlMapper.queryList();
        // 所有表的 Java 名称
        List<String> collect = list.stream().map(s -> {
            String tableJavaName = tableNameToJava(s);
            return tableJavaName;
        }).collect(Collectors.toList());
        module.put("tableJavaNames",collect);
        module.put("package",configProperties.getPackageName());
        module.put("moduleName",configProperties.getModuleName());
        module.put("tableName",tableInfo.get("tableName"));
        module.put("tableComment",tableInfo.get("tableComment"));
        module.put("className", tableNameToJava(tableInfo.get("tableName")));
        List<Map<String, String>> maps = new ArrayList<>();
        HashMap<String, String> pkMap = new HashMap<>();
        for (Map<String, String> map : columnsInfo) {
            HashMap<String, String> objectObjectHashMap = new HashMap<>();
            objectObjectHashMap.put("comment",map.get("columnComment"));
            objectObjectHashMap.put("attrName",columnToJava(map.get("columnName")));
            objectObjectHashMap.put("columnName",map.get("columnName"));
            objectObjectHashMap.put("attrType",typeConversionMap.get(map.get("dataType")));
            if (map.get("columnKey").equals("PRI")){
                pkMap.put("columnName",map.get("columnName"));
                pkMap.put("attrName",columnToJava(map.get("columnName")));
                pkMap.put("attrType",typeConversionMap.get(map.get("dataType")));
                if (map.get("EXTRA").equals("auto_increment")) {
                    pkMap.put("type","auto_increment");
                }else {
                    pkMap.put("type","");
                }
            }
            maps.add(objectObjectHashMap);
        }
        module.put("columns",maps);
        if (pkMap.size() <= 0) {
            pkMap.put("columnName",maps.get(0).get("tableName"));
            pkMap.put("type","");
        }
        module.put("pk",pkMap);
        return module;
    }

    /**
     * 字段名转java名称
     * @param columnName
     * @return
     */
    public String columnToJava(String columnName) {

        return get2Java(columnName);
    }

    public String get2Java(String columnName) {
        StringBuilder javaName = new StringBuilder();
        for (int i = 0; i < columnName.length(); i++) {
            char c = columnName.charAt(i);
            String s = String.valueOf(c);
            if ("_".equals(s) || "-".equals(s)) {
                // 需要跳一格了
                c = columnName.charAt(i + 1);
                s = String.valueOf(c);
                if (c >= 'a' && c <= 'z') {
                    javaName.append(s.toUpperCase());
                }else {
                    javaName.append(s);
                }
                i++;
            }else {
                javaName.append(s);
            }
        }
        return javaName.toString();
    }

    /**
     * 表名转 java 名称
     * @param tableName
     * @return
     */
    public String tableNameToJava(String tableName) {
        // todo 有前缀的情况
        if (!configProperties.getTablePrefix().equals("")) {
            if (tableName.startsWith(configProperties.getTablePrefix())) {
                tableName = tableName.substring(configProperties.getTablePrefix().length());
            }
        }
        return StrUtil.upperFirst(get2Java(tableName));
    }


    /**
     * 获取需要加载的 template 模板名称
     * @return
     */
    public List<String> getTemplates() {
        ArrayList<String> list = new ArrayList<>();
        list.add("controller.ftl");
        list.add("entity.ftl");
        list.add("mapper.ftl");
        list.add("mapperXml.ftl");
        list.add("mybatisPlusConfig.ftl");
        list.add("R.ftl");
        list.add("service.ftl");
        list.add("serviceImpl.ftl");
        list.add("request.ftl");
        list.add("router.ftl");
        list.add("vue.ftl");
        list.add("vueAddOrUpdate.ftl");
        list.add("webMvConfig.ftl");
        return list;
    }

    /**
     * 生成代码的比特流
     * @return
     */
    public byte[] generatorCodes() {
        // 1. 定义一个内存输出流S
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        // 2. 获取 ZipOutputStream
        ZipOutputStream zipOutputStream = new ZipOutputStream(byteArrayOutputStream);

        // 获取所有的表名
        List<String> tableNameList = sqlMapper.queryList();
        try {

            for (String tableName : tableNameList) {
                // 生成每个表的数据
                generateCode(zipOutputStream, tableName);
            }

            // 7. 将字节码返回
            zipOutputStream.close();
            return byteArrayOutputStream.toByteArray();
        } catch (IOException | TemplateException e) {
            e.printStackTrace();
        }


        return  null;
    }

    /**
     * 生成每个表的数据
     * @param zipOutputStream
     * @param tableName
     * @throws IOException
     * @throws TemplateException
     */
    private void generateCode(ZipOutputStream zipOutputStream, String tableName) throws IOException, TemplateException {
        // 3. 获取数据
        HashMap<String, Object> templateData = getTemplateData(tableName);
        // 4. 初始化 configuration
        Configuration configuration = new Configuration(Configuration.VERSION_2_3_22);
        configuration.setEncoding(Locale.CHINESE,"UTF-8");
        configuration.setDirectoryForTemplateLoading(new File(Thread.currentThread().getContextClassLoader().getResource("").getPath() + "templates/"));

        // 5. 获取 template 列表
        List<String> templates = getTemplates();
        // 6. 循环注入每个 template
        for (String templateName : templates) {
            Template template = configuration.getTemplate(templateName);
            // 在 zip 内部的位置, 如 com\hjy\test\config\MybatisPlusConfig.java
            String filePathInZip = getFilePath(template,templateData.get("className").toString());
            if (existTemplateName.contains(filePathInZip) ) {
                continue;
            }
            zipOutputStream.putNextEntry(new ZipEntry(filePathInZip));
            existTemplateName.add(filePathInZip);
            OutputStreamWriter outputStreamWriter = new OutputStreamWriter(zipOutputStream, Charset.defaultCharset());
            template.process(templateData,outputStreamWriter);
            zipOutputStream.closeEntry();
        }
    }

    /**
     * 获取对应的 template 在 zip 包中的位置
     * @param template
     * @param tableJavaName
     * @return
     */
    public String getFilePath(Template template,String tableJavaName) {
        // 包名 + 模块名 + entity/com.hjy.service/web/config/utils + 文件名
        String templateName = template.getName();

        String prefix = "";
        String suffix = "";
        if (configProperties.getModuleName().equals("")) {
            prefix = configProperties.getPackageName().replace(".",File.separator) + File.separator;

        }else  {
            prefix = configProperties.getPackageName().replace(".",File.separator) + File.separator + configProperties.getModuleName() +  File.separator;
        }

        if ("controller.ftl".equals(templateName)) {
            suffix = "web" + File.separator + tableJavaName + "Controller.java";
        }else if ("entity.ftl".equals(templateName)) {
            suffix = "entity" + File.separator + tableJavaName + ".java";
        }else if ("mapper.ftl".equals(templateName)) {
            suffix = "mapper" + File.separator + tableJavaName + "Mapper.java";
        }else if ("mapperXml.ftl".equals(templateName)) {
            prefix = "";
            suffix = "main" + File.separator + "resources" + File.separator + "mapper" +File.separator + tableJavaName +  "Mapper.xml";
        }else if ("mybatisPlusConfig.ftl".equals(templateName)) {
            suffix = "config" + File.separator +  "MybatisPlusConfig.java";
        }else if ("R.ftl".equals(templateName)) {
            suffix = "utils" + File.separator +  "R.java";
        }else if ("service.ftl".equals(templateName)) {
            suffix = "service" + File.separator + tableJavaName + "Service.java";
        }else if ("serviceImpl.ftl".equals(templateName)) {
            suffix = "service" + File.separator + "impl" + File.separator + tableJavaName + "ServiceImpl.java";
        }else if ("request.ftl".equals(templateName)) {
            prefix = "";
            // src/utils/request.js
            suffix = "src" + File.separator + "utils" + File.separator + "request.js";
        }else if ("router.ftl".equals(templateName)) {
            prefix = "";
            // src/utils/request.js
            suffix = "src" + File.separator + "router" + File.separator + "index.js";
        }else if ("vue.ftl".equals(templateName)) {
            prefix = "src" + File.separator + "view" + File.separator + "module"+ File.separator ;
            // src/view/module/test/Auth.vue
            if (configProperties.getModuleName().equals("")) {
                suffix = tableJavaName + ".vue";
            }else {
                suffix = configProperties.getModuleName() + File.separator + tableJavaName + ".vue";
            }
        }else if ("vueAddOrUpdate.ftl".equals(templateName)) {
            prefix = "src" + File.separator + "view" + File.separator + "module"+ File.separator ;
            // src/view/module/test/Auth.vue
            if (configProperties.getModuleName().equals("")) {
                suffix = tableJavaName + "AddOrUpdate.vue";
            }else {
                suffix = configProperties.getModuleName() + File.separator + tableJavaName + "AddOrUpdate.vue";
            }
        }else if ("webMvConfig.ftl".equals(templateName)) {
            suffix = "config" + File.separator + "WebMvConfig.java";
        }
        return prefix + suffix;
    }
}
